//
//  NMCache.swift
//  NetEaseMusic
//
//  Created by SAGESSE on 2020/2/23.
//  Copyright © 2020 SAGESSE. All rights reserved.
//

import UIKit


class NMCache: NSObject {
    
    
    static let shared: NMCache = .init()
    
    
    func remoteURL(_ url: URL?, complete: @escaping (URL?, Error?) -> ()) {
        
        guard let requestId = url?.queryValue(forKey: "id") else {
            complete(nil, URLSessionEx.error(NSURLErrorBadURL))
            return
        }
//        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
////            kCFErrorDomainCFNetwork
//            let a = NSError(domain: kCFErrorDomainCFNetwork as String, code: -1003, userInfo: nil)
//            let b = NSError(domain: kCFErrorDomainCFNetwork as String, code: -1009, userInfo:[
//                "_kCFStreamErrorDomainKey": 1,
//                "_kCFStreamErrorCodeKey": 50,
//            ]
//            )
//            let c = NSError(domain: NSURLErrorDomain, code: -1009, userInfo: [
//                "NSUnderlyingError": b,
//                "_kCFStreamErrorDomainKey": 1,
//                "_kCFStreamErrorCodeKey": 50,
//                "NSErrorFailingURLStringKey": url!.absoluteString,
//                "NSErrorFailingURLKey": url!,
//                "NSURL": url!,
//                "NSLocalizedDescription": "The Internet connection appears to be offline."
//            ])
//            
//            complete(nil, c)
//        }
//        return;

        switch url?.path {
        case "/url/video":
            // Hit the cache?
            if let task = Subtask.subtasks[requestId] {
                if task.vaild {
                    complete(task.resource?.url, task.error)
                    return
                }
                if task.waiting == nil {
                    task.waiting?.append(complete)
                    return
                }
                // Must remake request.
            }
            
            let task = Subtask(requestId)
            task.waiting = [complete]
            Subtask.subtasks[requestId] = task
            Subtask.batch()
            
        case "/url/movie":
            // Hit the cache?
            if let task = Subtask2.subtasks[requestId] {
                if task.vaild {
                    complete(task.resource?.url, task.error)
                    return
                }
                if task.waiting == nil {
                    task.waiting?.append(complete)
                    return
                }
                // Must remake request.
            }
            
            let task = Subtask2(requestId)
            task.waiting = [complete]
            Subtask2.subtasks[requestId] = task
            Subtask2.batch()
            
        case "/url/image":
            // Generate the real file url.
            guard let signature = URLSessionEx.Security.signature(requestId) else {
                complete(nil, URLSessionEx.error(NSURLErrorBadURL))
                return
            }
            
            var newValue = url.flatMap{ URLComponents(url: $0, resolvingAgainstBaseURL: true) }
            
            newValue?.scheme = "https"
            newValue?.host = "p1.music.126.net"
            newValue?.path = "/\(signature)/\(requestId).jpg"
            newValue?.queryItems?.removeAll {
                $0.name == "id"
            }
            
            complete(newValue?.url, nil)
            
        default:
            complete(nil, URLSessionEx.error(NSURLErrorBadURL))
        }
    }
    
    
    class Subtask {
        
        class Resource: Codable {
            
            var id: String
            var url: URL?
            var size: Int = 0
            var validityTime: Int = 0
            
            init(_ id: String) {
                self.id = id
            }
        }

        var id: String
        var error: Error?
        var expired: Date?
        var resource: Resource?

        init(_ id: String) {
            self.id = id
        }

        var waiting: [(URL?, Error?) -> ()]?
        var vaild: Bool {
            guard let expired = expired else {
                return false
            }
            return Date() <= expired
        }
        
        func complete(_ resource: Resource?, _ error: Error?) {
            
            self.error = error
            self.expired = Date(timeIntervalSinceNow: .init((resource?.validityTime ?? 3) * 60))
            self.resource = resource
            
            
            self.waiting?.forEach {
                $0(resource?.url, error)
            }
            self.waiting = nil
        }
        
        /// batch all pending request.
        static func batch() {
            
            guard !requesting else {
                return
            }
            
            let tasks = subtasks.values.filter { !$0.vaild }
            guard !tasks.isEmpty else {
                return
            }
            
            let url = "/api/cloudvideo/playurl"
            let param = [
                "ids": "\(tasks.map { $0.id })",
                "resolution": "480"
            ]
            
            self.requesting = true
            NMNetwork.shared.request(url, param) {
                self.requesting = false
                do {
                    // The prase remote resource returns the result.
                    guard let urls = try $0?.decode([Resource].self, forKey: "urls") else {
                        throw $1 ?? URLSessionEx.error(NSURLErrorBadServerResponse)
                    }

                    // The task is complete.
                    urls.forEach {
                        self.subtasks[$0.id]?.complete($0, nil)
                    }
                    
                } catch (let error) {
                    print(error)
                    // Mark all for failure.
                    tasks.forEach {
                        $0.complete(nil, error)
                    }
                }
//                self.batch()
            }
        }
        static var subtasks: [String: Subtask] = [:]
        static var requesting: Bool = false
    }
    class Subtask2 {
        
        class Resource: Codable {
            
            var id: String
            var url: URL?
            var size: Int = 0
            var expi: Int = 0
            
            init(_ id: String) {
                self.id = id
            }
        }

        var id: String
        var error: Error?
        var expired: Date?
        var resource: Resource?

        init(_ id: String) {
            self.id = id
        }

        var waiting: [(URL?, Error?) -> ()]?
        var vaild: Bool {
            guard let expired = expired else {
                return false
            }
            return Date() <= expired
        }
        
        func complete(_ resource: Resource?, _ error: Error?) {
            
            self.error = error
            self.expired = Date(timeIntervalSinceNow: .init((resource?.expi ?? 3) * 60))
            self.resource = resource
            
            self.waiting?.forEach {
                $0(resource?.url, error)
            }
            self.waiting = nil
        }
        
        /// batch all pending request.
        static func batch() {
            
            guard !requesting else {
                return
            }
            
            let tasks = subtasks.values.filter { !$0.vaild }
            guard !tasks.isEmpty else {
                return
            }

            let url = "/api/song/enhance/play/mv/urls"
            let param = [
                "ids": "\(tasks.map { $0.id })",
                "r": "480"
            ]
            
            self.requesting = true
            NMNetwork.shared.request(url, param) {
                self.requesting = false
                do {
                    // The prase remote resource returns the result.
                    guard let urls = try $0?.decode([Resource].self, forKey: "data") else {
                        throw $1 ?? URLSessionEx.error(NSURLErrorBadServerResponse)
                    }

                    // The task is complete.
                    urls.forEach {
                        self.subtasks[$0.id]?.complete($0, nil)
                    }
                    
                } catch (let error) {
                    print(error)
                    // Mark all for failure.
                    tasks.forEach {
                        $0.complete(nil, error)
                    }
                }
//                self.batch()
            }
        }
        static var subtasks: [String: Subtask2] = [:]
        static var requesting: Bool = false
    }

}


// MARK: -


fileprivate extension URL {
    
    func queryValue(forKey key: String) -> String? {
        guard let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems else {
            return nil
        }

        for item in queryItems where item.name == key {
            return item.value
        }
        
        return nil
    }
}
